#ifndef _TAGHandler_h_
#define _TAGHandler_h_

#include "shared.h" // Generic cross-project items (resuability)
#include "project_shared.h" // Scope is to all other jammit files, shared layout file, iml icons
#include "StringHandler.h"
#include <wctype.h>
#include <wchar.h>
#include <CtrlLib/CtrlLib.h>
#include <GridCtrl/GridCtrl.h>
#include <plugin/pcre/Pcre.h>   // Location of RegExp, strangely.  Add plugin/pcre package

using namespace Upp;

// Create a list of internal codes representing meaningful tags, a subset of all the ones available.  These are for internal use.

const int TAG_TITLE                              = 1;
const int TAG_TITLEGROUP                         = 2;
const int TAG_SUBTITLE                           = 3;
const int TAG_ARTISTNAME                         = 4;
const int TAG_ALBUMNAME                          = 5;
const int TAG_BANDNAME                           = 6;
const int TAG_COMPOSERNAME                       = 7;
const int TAG_PUBLISHERNAME                      = 8;
const int TAG_CONDUCTORNAME                      = 9;
const int TAG_MIXERNAME                          = 10;
const int TAG_RELEASEYEAR                        = 11;
const int TAG_LYRICS                             = 12;
const int TAG_GENRECODES                         = 13; // The list that came in TCON.  "(51)(52)"
const int TAG_GENRENAME                          = 14; // Either the string in TCON in parens or the lookup string of the number, but only the first one.
const int TAG_GENRENAMES                         = 15; // A vector of all the names.  The order may be lost, I don't know the vector rules
const int TAG_TRACKNO                            = 16; // Useful for ordering album playback
const int TAG_LANG                               = 17; // Language tag
const int TAG_PAIR                               = 18; // A tuple from TXXX descrpiption=string value
const int TAG_LENGTH                             = 19; // In milliseconds
const int TAG_UNREC                              = 20; // A tuple with the framename and value of something we weren't set up to process
const int TAG_COMMENT                            = 21; // All the comments rolled up
const int TAG_POSITIONINSET                      = 22;
const int TAG_BEATSPERMIN                        = 23;
const int TAG_RECORDINGDATE                      = 24;
const int TAG_ORIGINALARTIST                     = 25;
const int TAG_INTERNATIONALSTANDARDRECORDINGCODE = 26;
const int TAG_COPYRIGHT                          = 27;
const int TAG_ENCODINGSETTINGS                   = 28;
const int TAG_MUSICALKEY                         = 29;

const char *tagNames[] = {
	"{Error}",
	"Title", //const int TAG_TITLE           = 1;
	"TitleGroup", //const int TAG_TITLEGROUP      = 2;
	"SubTitle", //const int TAG_SUBTITLE        = 3;
	"Artist", //const int TAG_ARTISTNAME      = 4;
	"Album", //const int TAG_ALBUMNAME       = 5;
	"Band", //const int TAG_BANDNAME        = 6;
	"Composer", //const int TAG_COMPOSERNAME    = 7;
	"Publisher", //const int TAG_PUBLISHERNAME   = 8;
	"Conductor", //const int TAG_CONDUCTORNAME   = 9;
	"Mixer", //const int TAG_MIXERNAME       = 10;
	"ReleaseYear", //const int TAG_RELEASEYEAR     = 11;
	"Lyrics", //const int TAG_LYRICS          = 12;
	"Genre", //const int TAG_GENRENAME       = 13;
	"Track#", //const int TAG_TRACKNO         = 14; // Useful for ordering album playback
	"Language", //const int TAG_LANG            = 15; // Language tag
	"TagPair", //const int TAG_PAIR            = 16; // A tuple from TXXX descrpiption=string value
	"Length", //const int TAG_LENGTH          = 17;
	"Unrecognized",
	"Comment",
	"PositionInSet", //const int TAG_POSITIONINSET                      = 20;
	"BeatsPerMin", //const int TAG_BEATSPERMIN                        = 21;
	"RecordingDate", //const int TAG_RECORDINGDATE                      = 22;
	"OriginalArtist", //const int TAG_ORIGINALARTIST                     = 23;
	"InternationalStandardRecordingCode", //const int TAG_INTERNATIONALSTANDARDRECORDINGCODE = 24;
	"Copyright", //const int TAG_COPYRIGHT                          = 25;
	"EncodingSettings", //const int TAG_ENCODINGSETTINGS                   = 26;
	"MusicalKey", //const int TAG_MUSICALKEY                         = 27;
};
	
// Elements of the database or file, for convenience so we can pass them in the same vector with the MP3 TAGS

const int TAG_FILENAME    = 1117; 
const int TAG_FILEPATH    = 1118;
const int TAG_FILESIZE    = 1119;

struct MPEGHeaderStruct {
	unsigned int frameSync           : 11;
	unsigned int MPEGAudioVersionID  : 2;
	unsigned int layerDesc           : 2;
	unsigned int protectionBit		 : 1;
	unsigned int bitrateIdx          : 4;
	unsigned int samplingRateFreqIdx : 2;
	unsigned int paddingBit          : 1;
	unsigned int privateBit          : 1;
	unsigned int channelMode         : 2;
	unsigned int modeExtension       : 2;
	unsigned int copyright           : 1;
	unsigned int original            : 1;
	unsigned int emphasis            : 2;
};

#include "Tags.h"

union AltStruct {
	MPEGHeaderStruct MPEGHeader;
	int MPEGHeaderAsInt;
};

// char myhexarray1[]={0x1,0x2};
// char myhexarray[] ={"\x1\x2"};
// char myhexarray[] ={'\x1','\x2'};

struct MediaFileTypeStruct {
	unsigned int foundMPEGTag        : 2; // 0 = unknown; 1 = yes; 2 = no  FF (and E0)                        FF(E0)         http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm
	unsigned int foundMPEGPSTag      : 2; // MPEG-PS (aka Pack header)                                        000001BA
	unsigned int foundMP3Tag         : 2; //                                                                  FFFB           http://www.digitalpreservation.gov/formats/fdd/fdd000105.shtml
	unsigned int foundKOZTag         : 2; // Sprint
	unsigned int foundADIFTag        : 2;
	unsigned int foundADTSTag        : 2;
	unsigned int foundMP32Tag        : 2; //                                                                  FFFB30         http://www.digitalpreservation.gov/formats/fdd/fdd000105.shtml
	unsigned int foundID3V1Tag       : 2; // 128 bytes from end of file with TAG                              TAG            http://en.wikipedia.org/wiki/ID3
	unsigned int foundID3V11Tag      : 2; // Michael Mutschler's 1997 enhancement for genre list
	unsigned int foundID3V2Tag       : 2; // marker in first 3 bytes of the file                              ID3
	unsigned int foundID3V22Tag      : 2; // Obsolete format                                                  ID3/002        http://www.id3.org/id3v2-00
	unsigned int foundID3V23Tag      : 2; // marker in first 3 bytes of the file                              ID3/003        http://www.id3.org/d3v2.3.0
	unsigned int foundID3V24Tag      : 2; // marker in first 3 bytes of the file                              ID3/004        http://www.id3.org/id3v2.4.0-structure
	unsigned int foundAC3Tag         : 2; // Found a few                                                      0B77           http://www.atsc.org/cms/standards/a_52b.pdf
	unsigned int foundLAMETag        : 2; // Info then 128th byte is 'LAME'                                   Info..LAME     
	unsigned int foundFLACTag        : 2; //                                                                  fLaC           http://flac.sourceforge.net/ogg_mapping.html
	unsigned int foundWMATag         : 2; // 
	unsigned int foundInfoTag        : 2; // Indicates Xing?
	unsigned int foundXingTag        : 2; // Indicates Xing?
	unsigned int foundMP4Tag         : 2; // Worst
	unsigned int foundM4ATag         : 2;
	unsigned int foundWAVTag         : 2;
	unsigned int foundOGGTag         : 2;
	unsigned int foundRATag          : 2;
	unsigned int foundAPETag         : 2;
	byte * locMPEGTag;   int    bufMPEGTag;
	byte * locMPEGPSTag; int    bufMPEGPSTag;
	byte * locMP3Tag;    int    bufMP3Tag;
	byte * locKOZTag;    int    bufKOZTag;
	byte * locADIFTag;   int    bufADIFTag;
	byte * locADTSTag;   int    bufADTSTag;
	byte * locMP32Tag;   int    bufMP32Tag;
	byte * locID3V1Tag;  int    bufID3V1Tag;
	byte * locID3V11Tag; int    bufID3V11Tag;
	byte * locID3V2Tag;  int    bufID3V2Tag;
	byte * locID3V22Tag; int    bufID3V22Tag;
	byte * locID3V23Tag; int    bufID3V23Tag; // Buffer # that we found it in
	byte * locID3V24Tag; int    bufID3V24Tag;
	byte * locAC3Tag;    int    bufAC3Tag;
	byte * locLAMETag;   int    bufLAMETag;
	byte * locFLACTag;   int    bufFLACTag;
	byte * locWMATag;    int    bufWMATag;
	byte * locInfoTag;   int    bufInfoTag;
	byte * locXingTag;   int    bufXingTag;
	byte * locMP4Tag;    int    bufMP4Tag;
	byte * locM4ATag;    int    bufM4ATag;
	byte * locWAVTag;    int    bufWAVTag;
	byte * locOGGTag;	 int    bufOGGTag;
	byte * locRATag;     int    bufRATag;
	byte * locAPETag;    int	bufAPETag;
	
	unsigned int extractID3V23Tag         : 2;
	unsigned int titleTag                 : 2;
	unsigned int artistNmTag              : 2;
	unsigned int albumNmTag               : 2;
	unsigned int releaseYrTag             : 2;
};


// Coded for MPEG so far
// http://mpgedit.org/mpgedit/mpeg_format/mpeghdr.htm 
// Check out MediaInfo, libzen0, libmediainfo0 (source??)
// Missing on Cake mp3: id3v2 (2048), Frame 0 at F46 for 417 bytes) 3910 MPEG-1 Audio layer 3 m1a mpa1 mp1 m2a mpa2 mp2 mp3 
// 4)  (3905)  (3906)  (3907)  (3908)  (3909)  (3910) ? (3911)  (3912)  (3913) 1000100 (3914)  (3915)  (3916) 1 (3917) 101110 (3918) 1011 (3919)  (3920)  (3921) 11000 (3922) 1100000 

// Cracker mp3: Id3v2 (378 bytes), Frame 0 - Tag (Xing)(1044 bytes) at 17A (378) 

#define FOUNDFILETYPE    1
#define NOTFOUNDFILETYPE 2

#define TAGEXTRACTED  1
#define TAGEXTRACTERR 2

typedef VectorMap<int,Value> TagVector;
typedef VectorMap<String,Value> FrameVector;

class TagHandler {
public:
	static const int bufsz = 4096; // Difference between 4096 and 8192 is tripling the delay between files
	static const int bufcnt = 5; // buffers to read
	byte buffer[bufcnt][bufsz];        
	byte *bufferptr;
	int activebuf;
	int bytesconsumed;
	int maxbytes;
	bool readpasteofbuf;
	bool reachedeoffile;
	bool stopconsuming;  // When true, all calls to ConsumeBuf return false
	bool consumptionerror; // last consumption request failed to consume, so any target is unusable
	TagHandler() {
	}

	String tagDump;
	TagVector extractedTags;
	FrameVector extractedUnrecognizedFrames; // Ones we don't recognize the frame identifier
	
	enum enumTextEncoding { eteISO88591, eteUTF16, eteUTF16BE, eteUTF8 };
	enumTextEncoding lastdecoding; // We set this in the low-level so we can dump it before each tag, so we can track our decoding quality for UTF16, BE, etc.
	
	//============================================================================================================================
	//
	// Extract Tags - Extract MP3 and other tags from a file by scanning for strings and codes into a Vector
	//
	// tagDump - Long string of various information that I didn't want to clutter up the extractedTags Vector with.
	//============================================================================================================================
	int ExtractTags(String filePath) {
		tagDump.Clear();
		extractedTags.Clear();
		extractedUnrecognizedFrames.Clear();
		readpasteofbuf = false;
		reachedeoffile = false;
		stopconsuming = false;
		consumptionerror = false;
		maxbytes = 100;
		
		
		static int fcnt = 0;
		fcnt++;
		int fdesc = open(filePath, O_RDONLY|O_NONBLOCK); //Non-blocking, applies to open operation only
		if (fdesc == -1) {
            tagDump << "::failed to open O_RDONLY|O_NONBLOCK::" << strerror(errno);
			return -1;
		}
		
        int endbyte = lseek(fdesc, 0, SEEK_END);
        if (endbyte < 0) {
            tagDump << "::failed to SEEK_END::" << strerror(errno);
            close(fdesc);
            return -2;
        }

		tagDump << ":FILESIZE=" << endbyte << ";";
		
        if (endbyte <= 10000) {
            tagDump << "::TINYFILE::";
            close(fdesc);
            return -3;
        }
        
        // Grab the last 128 chars
        lseek(fdesc, -128, SEEK_SET);
        byte tailbuffer[128];
        int bytesread = (int)read(fdesc, (void *)&tailbuffer, sizeof(tailbuffer));
        
        lseek(fdesc, 0, SEEK_SET);

        struct MPEGHeaderStruct h;
        struct MediaFileTypeStruct mediaFileType;
        String lfilePath = filePath;
        
		memset((void *)&mediaFileType, 0, sizeof(MediaFileTypeStruct));

		int foundTagCount = 0; // Count each time we find a tag; since no tags mean we have no clues

        // Check the tail of the file for ID3V1 (Performance penalty?)

		byte *ptr = tailbuffer;
		byte *p = ptr;
		
		if (*p++ == 'T' && *p++ == 'A' && *p++ == 'G') {
			mediaFileType.foundID3V1Tag == FOUNDFILETYPE;
			mediaFileType.locID3V1Tag = ptr;
			tagDump << ":ID3v1:(TAG):";
			foundTagCount++;
		}
               
		// Grab a block of data and scan for ll ones.  This marks the beginning of an MPEG header

        // We will read several buffers into it to see if we can find the more obscure markers

        for (int j = 0; j < bufcnt; j++) {
	        bytesread = (int)read(fdesc, (void *)&buffer[j], sizeof(buffer[j]));
			if (bytesread < sizeof(buffer[j])) {
				LOG(CAT << "Error reading a lousy 4k from front of file.");
				Exclamation(CAT << "Exhausted " << filePath);
				close(fdesc);
				return -3;
			}
	
			for (ptr = buffer[j]; ptr < (buffer[j] + bufsz); ptr++) {

				p = ptr; // FF Ex
				if (!mediaFileType.locMPEGTag && ptr <= ((buffer[j] + bufsz) - 2) && *p++ == 0xFF && (*p++ & 0xE0) == 0xE0) {
					// Found MPEG
					mediaFileType.foundMPEGTag = FOUNDFILETYPE;
					mediaFileType.locMPEGTag = ptr;
					mediaFileType.bufMPEGTag = j;
					tagDump << ":MPEG:(FF&E0):";
					foundTagCount++;
				}

				p = ptr; // 49 44 33 03 00 00 00 (7) ID3....
				if (!mediaFileType.locKOZTag && ptr <= ((buffer[j] + bufsz) - 7) && *p++ == 0x49 && *p++ == 0x44 && *p++ == 0x33 && *p++ == 0x03 && *p++ == 0x00 && *p++ == 0x00 && *p++ == 0x00) {  // 2-byte reserve
					// Found Sprint Music Store (for mobile devices) KOZ is the same thing as AAC+ 3GPP AAC+
					// the format the author believes will eventually be the successor of Layer-3, namely MPEG-2 Advanced Audio Coding (AAC).
					// AAC begins
					// If you search on internet, you can find a lot of stuff about mp3, but only little about AAC. Why? It’s not because AAC is new, actually AAC is about 10 years old now. It’s because AAC is much complicated than mp3, there’s no easy way to explain AAC, even on its bitstream syntax. So, standards are your only teach. 
					// KOZ is audio flie format developed by Chaoticom and used by Groove Mobile for downloading songs to mobile phones
					// ADTS is similar to MP3 ( stream composed of different frames, and each frame having a syncword and header data previous to the actual AAC data).
					// ADIF is the RAW data packed inside a RIFF-like container (like PCM audio inside .wavs/.aiffs)
					// http://sourceforge.net/projects/faac/files/faad2-src/faad2-2.7/faad2-2.7.tar.gz/download
					mediaFileType.foundKOZTag = FOUNDFILETYPE;
					mediaFileType.locKOZTag = ptr;
					mediaFileType.bufKOZTag = j;
					tagDump << ":KOZ:";
					foundTagCount++;
				}

				p = ptr; 
				if (!mediaFileType.locADIFTag && ptr <= ((buffer[j] + bufsz) - 4) && *p++ == 'A' && *p++ == 'D' && *p++ == 'I' && *p++ == 'F') {
					// Found AAC-ADIF (KOZ)
					mediaFileType.foundADIFTag = FOUNDFILETYPE;
					mediaFileType.locADIFTag = ptr;
					mediaFileType.bufADIFTag = j;
					tagDump << ":AAC-ADIF:(KOZ):(ADIF):";
					foundTagCount++;
				}

				p = ptr; // FF Fx
				if (!mediaFileType.locADTSTag && ptr <= ((buffer[j] + bufsz) - 2) && *p++ == 0xFF && (*p++ & 0xF0) == 0xF0) {
					// Found AAC-ADTS (KOZ) else if (faad_showbits(&ld, 12) == 0xfff) {
					// http://lists.live555.com/pipermail/live-devel/2009-August/011113.html
					mediaFileType.foundADTSTag = FOUNDFILETYPE; // Found for Cracker song, also KOZ tag
					mediaFileType.locADTSTag = ptr;
					mediaFileType.bufADIFTag = j;
					tagDump << ":AAC-ADTS:(KOZ):(FF&F0):";
					foundTagCount++;
				}

				p = ptr;
				if (ptr == buffer[0] && !mediaFileType.locID3V2Tag && ptr <= ((buffer[j] + bufsz) - 3) && *p++ == 'I' && *p++ == 'D' && *p++ == '3') { 
					// Found ID3v2
					mediaFileType.foundID3V2Tag = FOUNDFILETYPE;
					mediaFileType.locID3V2Tag = ptr;
					mediaFileType.bufID3V2Tag = j;
					tagDump << ":ID3v2:";
					foundTagCount++;
					if (*p == 2) {
						// Found ID3v2.2.0
						mediaFileType.foundID3V22Tag = FOUNDFILETYPE;
						mediaFileType.locID3V22Tag = ptr;
						mediaFileType.bufID3V22Tag = j;
						tagDump << ":ID3v2.2.0:";
						foundTagCount++;
					} else if (*p == 3) {
						// Found ID3v2.3.0
						mediaFileType.foundID3V23Tag = FOUNDFILETYPE;
						mediaFileType.locID3V23Tag = ptr;
						mediaFileType.bufID3V23Tag = j;  // Save the buffer we found it in.
						tagDump << ":ID3v2.3.0:";
						foundTagCount++;
					} else if (*p == 4) {
						// Found ID3v2.4.0
						mediaFileType.foundID3V24Tag = FOUNDFILETYPE;
						mediaFileType.locID3V24Tag = ptr;
						mediaFileType.bufID3V24Tag = j;
						tagDump << ":ID3v2.4.0:";
						foundTagCount++;
					}
				}

				// syncword (synchronization word) from http://www.atsc.org/cms/standards/a_52b.pdf sect 5.4.1.1
				p = ptr;
				if (!mediaFileType.locAC3Tag && ptr <= ((buffer[j] + bufsz) - 2) && *p++ == 0x0B && *p++ == 0x77) {  // 2-byte reserve
					// Found AC-3
					mediaFileType.foundAC3Tag = FOUNDFILETYPE;
					mediaFileType.locAC3Tag = ptr;
					mediaFileType.bufAC3Tag = j;
					tagDump << ":AC3:";
					foundTagCount++;
				}
				
				p = ptr;
				if (!mediaFileType.locMPEGPSTag && ptr <= ((buffer[j] + bufsz) - 4) && *p++ == 0x00 && *p++ == 0x00 && *p++ == 0x01 && *p++ == 0xBA) {  // 2-byte reserve
					// Found MPEG-PS (aka Pack header)
					mediaFileType.foundMPEGPSTag = FOUNDFILETYPE;
					mediaFileType.locMPEGPSTag = ptr;
					mediaFileType.bufMPEGPSTag = j;
					tagDump << ":MPEG-PS:";
					foundTagCount++;
				}

				p = ptr; // 66 4C 61 43 00 00 00 22 (8) fLaC..."
				if (!mediaFileType.locFLACTag && ptr <= ((buffer[j] + bufsz) - 8) && *p++ == 0x66 && *p++ == 0x4C && *p++ == 0x61 && *p++ == 0x43 && *p++ == 0x00 && *p++ == 0x00 && *p++ == 0x00 && *p++ == 0x22) {
					// Found Flac
					mediaFileType.foundFLACTag = FOUNDFILETYPE;
					mediaFileType.locFLACTag = ptr;
					mediaFileType.bufFLACTag = j;
					tagDump << ":FLAC:(fLaC...):";
					foundTagCount++;
				}

				p = ptr;
				if (!mediaFileType.locXingTag && ptr <= ((buffer[j] + bufsz) - 4) && *p++ == 'X' && *p++ == 'i' && *p++ == 'n' && *p++ == 'g' ) { // Check for fLaC
					// Found Xing
					mediaFileType.foundXingTag = FOUNDFILETYPE;
					mediaFileType.locXingTag = ptr;
					mediaFileType.bufXingTag = j;
					tagDump << ":Xing:";
					foundTagCount++;
				}

				p = ptr;
				if (!mediaFileType.locLAMETag && ptr <= ((buffer[j] + bufsz) - 4) && *p++ == 'I' && *p++ == 'n' && *p++ == 'f' && *p++ == 'o' ) { // Check for Info
					p = &(ptr[120]);
					if (ptr <= ((buffer[j] + bufsz) - 124) && *p++ == 'L' && *p++ == 'A' && *p++ == 'M' && *p++ == 'E' ) {
						// Found LameInfo
						mediaFileType.foundLAMETag = FOUNDFILETYPE;
						mediaFileType.locLAMETag = ptr;
						mediaFileType.bufLAMETag = j;
						tagDump << ":LAME:(Info):";
						foundTagCount++;
					}
				}

				p = ptr; // 00 00 00 18 66 74 79 70 33 67 70 35   (12 bytes)  ....ftyp3gp5
				if (!mediaFileType.locMP4Tag && ptr <= ((buffer[j] + bufsz) - 12) && !*p++ && !*p++ && !*p++ && *p++ == 0x18 && *p++ == 'f' && *p++ == 't' && *p++ == 'p' && *p++ == '3' && *p++ == 'g' && *p++ == 'p' && *p++ == '5') { // Check for fLaC
					// Found MP4 MPEG-4 video
					mediaFileType.foundMP4Tag = FOUNDFILETYPE;
					mediaFileType.locMP4Tag = ptr;
					mediaFileType.bufMP4Tag = j;
					tagDump << ":MP4:(ftyp3gp5):";
					foundTagCount++;
				}

				p = ptr; // 00 00 00 20 66 74 79 70 4D 34 41 20 00 00 00 00 (16 bytes) ....ftypM4A ....
				if (!mediaFileType.locM4ATag && ptr <= ((buffer[j] + bufsz) - 16) && !*p++ && !*p++ && !*p++ && *p++ == 0x20 && *p++ == 'f' && *p++ == 't' && *p++ == 'p' && *p++ == 'M' && *p++ == '4' && *p++ == 'A' && *p++ == ' ' && !*p++ && !*p++ && !*p++ && !*p++) { // Check for fLaC
					// Found M4A Apple Lossless Audio Codec file
					mediaFileType.foundM4ATag = FOUNDFILETYPE;
					mediaFileType.locM4ATag = ptr;
					mediaFileType.bufM4ATag = j;
					tagDump << ":M4A:(ftypM4A):";
					foundTagCount++;
				}

				p = ptr; // 2E 52 4D 46 00 00 00 12 00 (9) .RMF.....
				if (!mediaFileType.locRATag && ptr <= ((buffer[j] + bufsz) - 9) && *p++ == 0x2E && *p++ == 0x52 && *p++ == 0x4D && *p++ == 0x46 && *p++ == 0x00 && *p++ == 0x00 && *p++ == 0x00 && *p++ == 0x12 && *p++ == 0x00) { // Check for 
					// RealAudio
					mediaFileType.foundRATag = FOUNDFILETYPE;
					mediaFileType.locRATag = ptr;
					mediaFileType.bufRATag = j;
					tagDump << ":RMF...:";
					foundTagCount++;
				}

				p = ptr; // 30 26 B2 75 8E 66 CF 11 A6 D9 00 AA 00 62 CE 6C (16)  
				if (!mediaFileType.locWMATag && ptr <= ((buffer[j] + bufsz) - 16) && *p++ == 0x30 && *p++ == 0x26 && *p++ == 0xB2 && *p++ == 0x75 && *p++ == 0x8E && *p++ == 0x66 && *p++ == 0xCF && *p++ == 0x11 && *p++ == 0xA6 && *p++ == 0xD9 && *p++ == 0x00 && *p++ == 0xAA && *p++ == 0x00 && *p++ == 0x62 && *p++ == 0xCE && *p++ == 0x6C) { // Check for 
					// Microsoft Windows Media
					mediaFileType.foundWMATag = FOUNDFILETYPE;
					mediaFileType.locWMATag = ptr;
					mediaFileType.bufWMATag = j;
					tagDump << ":WMA:";
					foundTagCount++;
				}

				p = ptr; // 4F 67 67 53 00 02 00 00 00 00 00 00 00 00 (14) OggS..........
				if (!mediaFileType.locOGGTag && ptr <= ((buffer[j] + bufsz) - 14) && *p++ == 0x4F && *p++ == 0x67 && *p++ == 0x67 && *p++ == 0x53 && *p++ == 0x00 && *p++ == 0x02 && *p++ == 0x00 && *p++ == 0x00 && *p++ == 0x00 && *p++ == 0x00 && *p++ == 0x00 && *p++ == 0x00 && *p++ == 0x00 && *p++ == 0x00) { // Check for 
					// Microsoft Windows Media
					mediaFileType.foundOGGTag = FOUNDFILETYPE;
					mediaFileType.locOGGTag = ptr;
					mediaFileType.bufOGGTag = j;
					tagDump << ":OGG:(OggS...):";
					foundTagCount++;
				}

				p = ptr; // 52 49 46 46 xx xx xx xx 57 41 56 45 66 6D 74 20 (16) RIFF....WAVEfmt
				if (!mediaFileType.locOGGTag && ptr <= ((buffer[j] + bufsz) - 16) && *p++ == 0x52 && *p++ == 0x49 && *p++ == 0x46 && *p++ == 0x46 && *p++ && *p++ && *p++ && *p++ && *p++ == 0x57 && *p++ == 0x41 && *p++ == 0x56 && *p++ == 0x45 && *p++ == 0x66 && *p++ == 0x6D && *p++ == 0x74 && *p++ == 0x20) { // Check for 
					// Microsoft Windows Media
					mediaFileType.foundOGGTag = FOUNDFILETYPE;
					mediaFileType.locOGGTag = ptr;
					mediaFileType.bufOGGTag = j;
					tagDump << ":OGG:(RIFF...WAVEfmt):";
					foundTagCount++;
				}
				
				p = ptr; // 41 50 45 54 41 47 45 58 (8) APETAGEX
				if (!mediaFileType.locAPETag && ptr <= ((buffer[j] + bufsz) - 8) && *p++ == 0x41 && *p++ == 0x50 && *p++ == 0x45 && *p++ == 0x54 && *p++ == 0x41 && *p++ == 0x47 && *p++ == 0x45 && *p++ == 0x58) {
					// Ape audio
					mediaFileType.foundAPETag = FOUNDFILETYPE;
					mediaFileType.locAPETag = ptr;
					mediaFileType.bufAPETag = j;
					tagDump << ":APE:";
					foundTagCount++;
				}
				
				//CC8("LYRICSEN")) =>Lyrics3 size_t Pos=Buf.find("LYRICSBEGIN")
				//Lyrics3v2 CC8("LYRICS20"))
				////Id3v1 if (CC3(Base->Buffer+((size_t)((Base->File_Size-128)-Base->File_Offset)))==CC3("TAG"))
			}
	    }
        
        close(fdesc);
        
        // Did we find any tags? (Don't count MP3 tags, since they don't give us what type of audio it is, exactly.
        
        if ((foundTagCount - (mediaFileType.foundID3V1Tag + mediaFileType.foundID3V2Tag + mediaFileType.foundMP3Tag)) == 0) {
	        tagDump << "::Error:Did not find any tags:";
	        return -1;
        }
        
        int rtncode = DecodeTags(mediaFileType);
        
        if (rtncode < 0) return rtncode;
        
        return 0;
	}
	
	//============================================================================================================================
	//
	// Decode Tags - Take discovered tag points in the buffer and decode all the codes within each one.
	//
	//============================================================================================================================
	int DecodeTags(MediaFileTypeStruct &mediaFileType) {
        // Pointers are into buffer sections (huge memory pig)
            
        if (mediaFileType.foundID3V23Tag == FOUNDFILETYPE) {
        	// If ID3 found, start strippin tags
        	// Decode the header:
        	
        	InitBuf(mediaFileType.locID3V23Tag, mediaFileType.bufID3V23Tag, 4000); // the buffer array so far, the pointer to where we found the tag, the buffer we found it in.
        	if (!SkipAheadBuf(3)) {
        		tagDump << "::Error:Consuming ID3 tag identifier failed:";
        		return -13;
        	}
        	
        	byte majVer;
        	if (!ConsumeBuf(sizeof(majVer), &majVer)) {
        		tagDump << "::Error:Consuming majorversion failed:";
        		return -14;
        	}
        	byte minVer;
        	if (!ConsumeBuf(sizeof(minVer), &minVer)) {
        		tagDump << "::Error:Consuming minorversion failed:";
        		return -15;
        	}
        	byte flags;
        	if (!ConsumeBuf(sizeof(flags), &flags)) {
        		tagDump << "::Error:Consuming flags failed:";
        		return -16;
        	}
        	
        	if ((flags & 0x1F) != 0) { // Only 3 MSB flag bits allowed in v2.3
        		tagDump << "::Error:Invalid flag of 3 MSB bits for v2.3";
        		return -17;
        	}
        	
        	bool usesunsynchronization = (flags & 0x80); // indicates if unsynch is used
        	if (usesunsynchronization) tagDump << ":uses unsynchronization:";
        	bool hasextendedhdr = (flags & 0x40);
        	if (hasextendedhdr) tagDump << ":has extended header:";
        	bool experimentalbit = (flags & 0x20);
        	if (experimentalbit) tagDump << ":experimental bit:";
        	// bit 4 = footer present in 3v2.4.0
        	
			// Get tag size
		    long tagsz = PopSyncSafe32();
		    
        	tagDump << ":TAGSIZE=" << tagsz << ";";
        	SetMaxBytes(tagsz + 10 ); // Notify Consumer what the bytes we'll be consuming (10 byte header)
        	
        	// Absorb Extended header
        	
        	if (hasextendedhdr) {
        		long exthdrsz = PopSyncSafe32();
        		
        		SkipAheadBuf(exthdrsz - 4); // Skip ahead for now, minus the width of the header size code
        	}

			String allcomments;
        	
        	while (IsConsumptionAllowed()) {
        		tagDump << ":@" << bytesconsumed << ";"; // Log are depth in the tag to debug overruns
        		uint32 frameid;
        		if (!ConsumeBuf(frameid)) {
        			tagDump << "::Error:Consuming frameid failed:";
        			return -4;
        		}
        		
        		// Check for padding start
        		if (!frameid) {  // Padding??
        			tagDump << ":Hit padding:";
        			break; // End ID3V23 read; should skip ahead.
        		} else {
            		String frameName = ConvertB4ToStr(frameid); 
					
					tagDump << ToHex(frameid);  // The contents are very helpful when an invalid frame name is present

					if (!ValidFrameName(frameName))	{     	
						// Show us the readable characters after the broken frame name so we can see if our sync is off
						tagDump << ":Remaining:" << ConsumeBufUnknownToString(100);
            			break; // Cancel whole op; we are off sync
            		}

            		tagDump << ":" << frameName << ":";
            		
            		// For 2.4 we have to un-sync-safe the size
            		uint32 frmsz;
            		if (!ConsumeBuf(frmsz)) {
            			tagDump << "::Error:Consuming framesize failed:";
            			return -6;
            		}
            		
            		uint16 frmflg;
            		if (!ConsumeBuf(frmflg)) {
            			tagDump << "::Error:Consuming frameflag failed:";
            			return -8;
            		}
            		
	            	bool discardthisframeiftagaltered = (flags & 0x8000); // What to do if the tag is altered (?)
	            	if (discardthisframeiftagaltered) tagDump << ":frame flagged as discard if tag altered:";
	            	bool discardthisframeiffilealtered = (flags & 0x4000); // Discard this frame if the audio altered (but not replaced)
	            	if (discardthisframeiffilealtered) tagDump << ":frame flagged as discard if file altered:";
	            	bool frameisreadonly = (flags & 0x2000);  // Don't change this frame unless you know what it is
	            	if (frameisreadonly) tagDump << ":frame is read-only:";

					bool frameiscompressed = (flags & 0x80); // zlib w 4 bytes for decompressed size appended to frame header
					if (frameiscompressed) tagDump << ":frame is compressed:";
					bool frameisencrypted = (flags & 0x40); // doh!
					if (frameisencrypted) tagDump << ":frame is encrypted:";
					bool frameispartofgroup = (flags & 0x20); // group identifier byte added to frame header
					if (frameispartofgroup) tagDump << ":frame in group:";
					
            		switch (frameid) {
            			// http://phoxis.org/2010/05/08/what-are-id3-tags-all-about/#id3v1, best site on this.
            			// Since this is a text frame, the first byte of the stored data will contain the text encoding. 
            			// 0x00=ISO-8859-1
            			// 0x01=Unicode
            		case Elements::TIT2: AppendTag(TAG_TITLE, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break; // Read unterminated since some are terminated early and then the remaining zeros won't be consumed, leaving point in bad spot
            		case Elements::TIT1: AppendTag(TAG_TITLEGROUP, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::TIT3: AppendTag(TAG_SUBTITLE, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::TPE1: AppendTag(TAG_ARTISTNAME, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::TALB: AppendTag(TAG_ALBUMNAME, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::TPE2: AppendTag(TAG_BANDNAME, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::TCOM: AppendTag(TAG_COMPOSERNAME, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::TPUB: AppendTag(TAG_PUBLISHERNAME, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::TPE3: AppendTag(TAG_CONDUCTORNAME, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::TPE4: AppendTag(TAG_MIXERNAME, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::TYER: AppendTag(TAG_RELEASEYEAR, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::TCON: 
            			{
            				String contentTypes = ConsumeTextEncFrstBytUntermStr(frmsz);
            				String genreName;
            				
		            		// May be "(RX)" for Remix or "(CR)" for Cover
		            		// Can be "(51)(53)" etc
            				// Process and verify format is "(99)"
            				int contentTypeAsNo;
            				RegExp contentTypeCode("^\\([0-9]+\\)$");
            				ValueArray genreList; // Keep an array of the genres as a Value-type
            				int genreFoundCount = 0;
            				
            				// Fetch all different types listed
            				while (contentTypeCode.GlobalMatch(contentTypes)) {
            					if (contentTypeCode.GetCount() == 1) {
            						genreFoundCount++;
	            					contentTypeAsNo = atoi(contentTypeCode[0]);
	            					genreName = contentTypeCodeList[contentTypeAsNo].name;
            						genreList.Add(genreName);
			            		    
	            					if (genreFoundCount == 1) {
	            						AppendTag(TAG_GENRENAME, frameName, genreName, frmsz); // Add the first one we find as the primary
	            					} else if (genreFoundCount > 1) {
	            						DumpCustomTag(frameName, genreName, frmsz);
	            					}
	            				}
            				}
	            				
            				AppendTag(TAG_GENRECODES, frameName, contentTypes, frmsz); // We keep both.  The codes can be a list of several parened items
            				extractedTags.Add(TAG_GENRENAMES, genreList);    // Here's the list (may be zero items)
            				
            			}
            			break;
            		case Elements::TRCK: AppendTag(TAG_TRACKNO, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::TLAN: AppendTag(TAG_LANG, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::TLEN: AppendTag(TAG_LENGTH, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
					case Elements::TPOS: AppendTag(TAG_POSITIONINSET, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
					case Elements::TBPM: AppendTag(TAG_BEATSPERMIN, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
					case Elements::TDAT: AppendTag(TAG_RECORDINGDATE, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
					case Elements::TOPE: AppendTag(TAG_ORIGINALARTIST, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
					case Elements::TSRC: AppendTag(TAG_INTERNATIONALSTANDARDRECORDINGCODE, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
					case Elements::TCOP: AppendTag(TAG_COPYRIGHT, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
					case Elements::TENC: AppendTag(TAG_ENCODINGSETTINGS, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
					case Elements::TKEY: AppendTag(TAG_MUSICALKEY, frameName, ConsumeTextEncFrstBytUntermStr(frmsz), frmsz); break;
            		case Elements::USLT: 
            			{
            				// Unsyncronized lyrics or text
            				
	            			byte encoding;
							if (!ConsumeBuf(1, &encoding)) {
								tagDump << "::Error:Consuming unsyncronized lyrics/text encoding failed:";
								return -42;
							}

	            			byte lang[4]; // "eng"
							if (!ConsumeBuf(3, lang)) {
								tagDump << "::Error:Consuming unsyncronized lyrics/text language code failed:";
								return -43;
							}

							lang[3] = 0;
							
	            			byte lyrics[frmsz];
	            			if (!ConsumeBuf(frmsz-4, lyrics, 
	            				false, // Don't stop on null 
	            				true)) { // Turn unprintable characters into dots.
	            				tagDump << "::Error:Consuming unsyncronized lyrics/text content failed:";
								return -44;
	            			}
	            			AppendTag(TAG_LYRICS, frameName, String((const char *)lyrics, frmsz-4), frmsz);
            			}
            			break;
            		case Elements::COMM:  // Comment
	            		{
	            			/*
	            				Interesting comments
	            				
	            				C.D.D.B. .D.i.s.c. .I.D.....2.2.1.1.8.2.1.3.
	            			*/
	            			byte encoding;
							if (!ConsumeBuf(1, &encoding)) {
								tagDump << "::Error:Consuming comment encoding failed:";
								return -42;
							}

	            			byte lang[4]; // "eng"
							if (!ConsumeBuf(3, lang)) {
								tagDump << "::Error:Consuming comment language code failed:";
								return -43;
							}

							lang[3] = 0;
							
	            			byte comment[frmsz];
	            			ConsumeBuf(frmsz-4, comment, 
	            				false, // Don't stop on null 
	            				true); // Turn unprintable characters into dots.
	            			DumpCustomTag(frameName, String((const char *)comment, frmsz), frmsz);
	            			allcomments << String((const char *)comment, frmsz) << "; ";
	            		}
	            		break;
            		case Elements::MCDI:  // Could be very useful: Music CD identifier, up to 804 bytes
            		case Elements::GEOB:  // General encapsulated object (MME
					case Elements::APIC:  // Attached picture
						{
							SkipAheadBuf(frmsz);
							extractedUnrecognizedFrames.Add(frameName, "...");
						}
						break;
            		case Elements::TXXX: // A terminated string label followed by a fixed-length string value
						{
	            		    String tagdesc;
	            		    String tagvalue;
	            		    if (!ConsumeTextEncFrstByt2Str(frmsz, tagdesc, tagvalue)) {
	            		        tagDump << "::Error:TXXX pair consumption failed:";
	            		        return -30;
	            		    }
	            		    
	            		    if (!consumptionerror) {
		            		    Vector<Value> vv;
		            		    vv << tagdesc;
		            		    vv << tagvalue;
		            		    ValueArray va(vv);
		            		    
		            		    extractedTags.Add(TAG_PAIR, va);
		            		    DumpCustomTag(frameName, tagdesc, frmsz); // Don't dump value since we don't really know what this is
		            		    DumpCustomTag(frameName, tagvalue, frmsz); // Don't dump value since we don't really know what this is
	            		    }
						}
            			break;
            		default:
            			if (frmsz > 1000000) {
            				tagDump << "framesize blowout";
            				break;
            			}
            			// Other tags found: RGAD (rarely used?), PRIV (String?), POPM, WXXX
            			
            			String unrecognizedFrameValue = ConsumeBufUnknownToString(frmsz);
            			DumpNewTag(frameName,unrecognizedFrameValue, frmsz);
            			extractedUnrecognizedFrames.Add(frameName, unrecognizedFrameValue);
            			
            		}
        		}
        		// 4 bytes of size 
        		// 2 bytes of flag
        		// x bytes of data
        		                                
        	} // End foundID3v23type
        	
			if (!allcomments.IsEmpty()) {
				AppendTag(TAG_COMMENT, "TXXX", allcomments);
			}
			
        // Vorbis comments, 2nd header packet The header packets are, in order, the identification header, the comments header, and the setup header.
        } else if (mediaFileType.foundOGGTag == FOUNDFILETYPE) {
        	// Decode the header:
        	
        	InitBuf(
        		0, // Start at first byte
        		0, // Buffer to start with
        		4000 // Don't go deeper than this
        		); // the buffer array so far, the pointer to where we found the tag, the buffer we found it in.
        		
			if (ConsumeStringBuf(4) != "OggS") { // http://www.xiph.org/ogg/doc/framing.html
				tagDump << "Ogg syncword not found";
				return -54;
			}
			
			byte streamStructureRevision = ConsumeByteBuf();
			
			if (streamStructureRevision) {
				tagDump << "Only streamStructureRevision 0 supported";
				return -55;
			}
			
			byte headerTypePacket = ConsumeByteBuf();
			
			if (headerTypePacket & 0xF8 != 0) {
				tagDump << "Unrecognized setting of flags in headerTypePacket";
				return -56;
			}
			
			uint64 absoluteGranulePosition = Consume64LSB();
			uint32 streamSerialNumber = Consume32LSB();
			uint32 pageSequenceNumber = Consume32LSB();
			uint32 pageChecksum = Consume32LSB();
			
			byte pageSegmentCount = ConsumeByteBuf();
			
        	DumpData(ConsumeBufUnknownToString(100), 100);
        	
        }

		
        // Pointer is in to the tailbuffer
        if (mediaFileType.foundID3V1Tag == FOUNDFILETYPE) {
            byte *tag = mediaFileType.locID3V1Tag;
            byte *p = mediaFileType.locID3V1Tag + 3; // Skip ID3
            String title = ConsumeUntermStr(30); 
            p+= 30;
            // artist, album, year, comment 28/30, zero-byte,track, genre
        }

        // Else tags for OGG, FLAC, WMA?
        // OGG: The vector is stored in second header packet in a Vorbis bitstream, UTF-8 [TAG]=[VALUE] (aka Vorbis comments). vector of 8-bit-clean strings.
        // FLAC:
        // WMA: find -name '*wma' -exec ffmpeg -i {} -acodec vorbis -ab 128k {}.ogg \;
        // M4A:
        // Monkey audio??
        // MP3Pro?
        // AIFF?
        // Broadcast Wave Format/RIFF Chunk
        // MP1? MP2?
        // APEv2?
        
        // All tags found;
        // a) check if title is better than database title.
        	// Popup to user, show both, let them pick.
        return 0;
	}

	//============================================================================================================================
	bool ValidFrameName(String frameName) {
		if (!IsPrintable(frameName)) {
			tagDump << "::Error:frame name not printable:";
			return false;
		}
		
		if (frameName.GetLength() > 4) {
			tagDump << "::Error:" << frameName << ": Too long:";
			return false;
		}
		
		if (frameName.GetLength() < 4) {
			tagDump << "::Error:" << frameName << ": Too short:";
			return false;
		}
		
		if (!IsUpper(frameName)) {
			tagDump << "::Error:" << frameName << ": Must be uppercase:";
			return false;
		}

		for (int i=0; i < 4; i++) {
			if (!isalnum(frameName[i])) {
				tagDump << "::Error:" << frameName << ": Must be alphanumeric:";
				return false;
			}
		}
		
		return true;
	}
	
	//============================================================================================================================
	long PopSyncSafe32() {
		uint32 val;
	    if (!ConsumeBuf(val)) {
	        tagDump << "::Error:Consuming val failed:";
	    	return -3;
	    }
	    
	    // Compensate/crunch for the 7-bit meaningful (28 bit) number
	    long syncval=((val>>0)&0x7F)
	             | ((val>>1)&0x3F80)
	             | ((val>>2)&0x1FC000)
	             | ((val>>3)&0x0FE00000);
		return syncval;
	}
	
	//============================================================================================================================
	String ConsumeBufUnknownToString(int bytes) {
		byte arr[bytes];
		ConsumeBuf(bytes, arr, false, true);
		return String(arr, bytes);
	}
	
	//============================================================================================================================
	void AppendTag(int tagid, String tagHdr, String tagValue, int framesize = -1) {
		extractedTags.Add(tagid, tagValue);
		String hexTagValue = ToHex(tagValue.Left(20));
		if (tagValue.GetLength() > 20) hexTagValue+= "...";
		tagDump << "={" << tagValue << "} x" << hexTagValue;
		
		if (framesize >= 0) {
			tagDump << " (" << framesize << ")-";
		}
		
		switch (lastdecoding) {
		case eteISO88591:
			tagDump << "ISO-8859-1; "; // ISO-8859-1 was incorporated as the first 256 code points of ISO/IEC 10646 and Unicode
			break;
		case eteUTF16:
			tagDump << "UCS-2/UTF-16 w/BOM; ";
			break;
		case eteUTF16BE:
			tagDump << "UTF-16BE wo/BOM; ";
			break;
		case eteUTF8:
			tagDump << "UTF-8; ";
			break;
		default:
			tagDump << "unrec encoding; ";
		}
	}
	
	//============================================================================================================================
	void DumpData(String data, int datasize) {
		tagDump << data << "(" << datasize << "); ";
	}

	//============================================================================================================================
	void DumpTag(String tagHdr, int framesize) {
		tagDump << "(" << framesize << "); ";
	}

	//============================================================================================================================
	void DumpGarbageTag(String tagHdr, String tagValue) {
		tagDump << "(Garbage)={" << tagValue << "}; ";
	}

	//============================================================================================================================
	void DumpNewTag(String tagHdr, String tagValue, int framesize) {
		tagDump << "(Unrec.)={" << tagValue << "} (" << framesize << "); ";
	}

	//============================================================================================================================
	void DumpCustomTag(String tagHdr, String tagValue, int framesize) {
		tagDump << "(Custom)={" << tagValue << "} (" << framesize << "); ";
	}

	//============================================================================================================================
	String ConvertB4ToStr(uint32 ui) {
		String r;
		r.Cat(((byte *)&ui)[3]);
		r.Cat(((byte *)&ui)[2]);
		r.Cat(((byte *)&ui)[1]);
		r.Cat(((byte *)&ui)[0]);
		return r;
	}
	//============================================================================================================================
	//
	//  Is Printable characters?
	//
	//  Make sure there's no garbage characters in there.
	//============================================================================================================================
	bool IsPrintable(String &s) {
		for (int i = 0; i < s.GetLength(); i++) {
			if (!isprint(s[i]))
				return false;
		}
		
		return true;
	}

	//============================================================================================================================
	bool IsConsumptionAllowed() {
		if (bytesconsumed >= maxbytes) return false;
		if (stopconsuming) return false;
		if (consumptionerror) return false;
		if (reachedeoffile) return false;
		return true;
	}

	//============================================================================================================================
	bool IsEndOfTag() {
		return (bytesconsumed >= maxbytes);
	}
	
	//============================================================================================================================
	void SetMaxBytes(int lmaxbytes) {
		maxbytes = lmaxbytes;
	}
	
	//============================================================================================================================
	void InitBuf(byte *p, int pinbuf, int lmaxbytes) {
		// if p is zero, set to first byte of the pin buf
		assert(pinbuf < bufcnt);
		if (!p) {
			p = buffer[pinbuf];
		}
		bufferptr = p;
		maxbytes = lmaxbytes;
		activebuf = pinbuf; // Trust it to be correct pointing to this buffer
		bytesconsumed = 0;
	}
	
	//============================================================================================================================
	bool SkipAheadBuf(int bytes) {
		return ConsumeBuf(bytes, (byte *)NULL, false, false);
	}

	//============================================================================================================================
	//  Consume 8-bit string buffer directly into a string for convenience (too much code!)
	//============================================================================================================================
	String ConsumeStringBuf(int bytes) {
		byte str[bytes];
		if (!ConsumeBuf(bytes, str, 
		            				false, // Don't stop on null 
		            				true)) { // Turn unprintable characters into dots.
		            				tagDump << "::Error:Consuming string failed:";
									return String::GetVoid();
		}
		DumpData((const char *)str, bytes);
		return String(str, bytes);
	}
	// pre-state: pointing at unconsumed byte
	// post-state: pointing at next byte to consume
	
	//============================================================================================================================
	//  Consume 1 8-bit byte and handle any errors
	//============================================================================================================================
	byte ConsumeByteBuf() {
		byte b;
		if (!ConsumeBuf(1, &b, 
		            				false, // Don't stop on null 
		            				true)) { // Turn unprintable characters into dots.
		            				tagDump << "::Error:Consuming byte failed:";
									return 0;
		}
		
		return b;
	}
	
	//============================================================================================================================
	bool ConsumeBuf(int bytes, byte *target = NULL, bool tilNull = false, bool dotUnprintables = false, bool unicode = false) {
		// Don't try to consume if blocked
		if (stopconsuming) {
			consumptionerror = true; // Unable to process request; must assume it was for a required field
			if (target) {
				memset(target, 0, bytes); // Flush target to reduce failures due to corrupt target values
			}
			return false;
		}
				
		int bytesremaining = bytes;
		
		if (bytesconsumed + bytes > maxbytes) {
			consumptionerror = true;
			stopconsuming = true;
			if (target) {
				memset(target, 0, bytes); // Flush target to reduce failures due to corrupt target values
			}
			return false;  // Trying to read past end of range
		}

		int actuallyconsumed = 0;
		int amounttoconsume = 0;
		bool hitnull;
nextchunk:
		int amountremaininginbuf = ((buffer[activebuf] + bufsz) - bufferptr);
		
		if (bytesremaining >= amountremaininginbuf) {
			amounttoconsume = amountremaininginbuf;
		} else {
			amounttoconsume = bytesremaining;
		}
		
		assert(amounttoconsume >= 0);
		
		hitnull = false;
		
		if (target) {
			if (tilNull) {
				if (unicode) {
					int nullpoint = 0;
					for (nullpoint = 0; nullpoint < amounttoconsume; nullpoint+=2) {
						if (!bufferptr[nullpoint] && !bufferptr[nullpoint+1]) {
							hitnull = true;
							break; // found terminator
						}
					}
					
					memcpy((char *)target, (const char *)bufferptr, nullpoint+2);  // Copy the zeros, too
					actuallyconsumed = nullpoint; 
					if (hitnull) {
						actuallyconsumed+= 2; // absorb the trailing zeros if found
					}
				} else {
					strncpy((char *)target, (const char *)bufferptr, amounttoconsume);
					
					actuallyconsumed = strnlen((const char *)target, amounttoconsume);
					if (actuallyconsumed < amounttoconsume) {
						hitnull = true;
						actuallyconsumed++; // consume the null
					}
				}
			} else {
				memcpy(target, bufferptr, amounttoconsume);
				actuallyconsumed = amounttoconsume;
			}
			if (dotUnprintables) {
				for (int i = 0; i < amounttoconsume; i++) {
					if (!isprint(target[i])) {
						target[i] = '.';
					}
				}
			}
		} else {
			actuallyconsumed = amounttoconsume;
		}

		bytesremaining-= actuallyconsumed;
		bufferptr+= actuallyconsumed;
		bytesconsumed+= actuallyconsumed;

		if (actuallyconsumed == amountremaininginbuf) {
			activebuf++;
		}
		
		if (hitnull) { // We're done
			return true;
		}
		
		if (bytesremaining == 0) {
			return true;
		}
		
		if (bytesremaining < 0) { // Went over; happens with wchar_t??
			return true;
		}
		
		if (activebuf >= bufcnt) { // past last buffer; cycle round and overwrite?
			stopconsuming = true; // Tell callers to stop consuming;
			consumptionerror = true;
			if (target) {
				memset(target, 0, bytes); // Flush target to reduce failures due to corrupt target values
			}
			return false; // consumption failed
		}
		
		bufferptr = buffer[activebuf];
		goto nextchunk;
	}
	
	//============================================================================================================================
	//  Grab 8 little-endian bytes
	//============================================================================================================================
	uint64 Consume64LSB() {
		byte l[8];
		uint64 b;
		if (!ConsumeBuf(8, l)) return 0;
	    b =           (uint8)l[7];
	    b=(b<<8)|(uint8)l[6];
	    b=(b<<8)|(uint8)l[5];
	    b=(b<<8)|(uint8)l[4];
	    b=(b<<8)|(uint8)l[3];
	    b=(b<<8)|(uint8)l[2];
	    b=(b<<8)|(uint8)l[1];
	    b=(b<<8)|(uint8)l[0];
	    return b;
	}

	//============================================================================================================================
	//  Grab 4 little-endian bytes
	//============================================================================================================================
	uint64 Consume32LSB() {
		byte l[4];
		uint64 b;
		if (!ConsumeBuf(4, l)) return 0;
	    b =           (uint8)l[3];
	    b=(b<<8)|(uint8)l[2];
	    b=(b<<8)|(uint8)l[1];
	    b=(b<<8)|(uint8)l[0];
	    return b;
	}

	//============================================================================================================================
	//  Grab 4 big-endian bytes
	//============================================================================================================================
	bool ConsumeBuf(uint32 &b) {
		byte l[4];
		if (!ConsumeBuf(4, l)) return false;
	    b =           (uint8)l[0];
	    b=(b<<8)|(uint8)l[1];
	    b=(b<<8)|(uint8)l[2];
	    b=(b<<8)|(uint8)l[3];
	    return true;
	}

	//============================================================================================================================
	//  Grab 2 big-endian bytes
	//============================================================================================================================
	bool ConsumeBuf(uint16 &b) {
		byte l[2];
		if (!ConsumeBuf(2, l)) return false;
	    b            =     (uint8)l[0];
	    b=(b<<8)|(uint8)l[1];
	    return true;
	}

	//============================================================================================================================
	//  Read text where encoded first byte defines encoding with possibly null-terminated string.
	//============================================================================================================================
	String ConsumeTextEncFrstBytTermStr(int nofchar) {
		byte encoding;
		if (!ConsumeBuf(1, &encoding)) {
			tagDump << "::Error:Consuming terminated string failed:";
			return String::GetVoid();
		}
		
		switch (encoding) {
		case 0:
			lastdecoding = eteISO88591;
			return ConsumeTermStr(nofchar-1); // Deduct encoding byte
		case 1:
			lastdecoding = eteUTF16;
			return ConsumeUntermStrUTF16(nofchar-1);
		case 2:
			lastdecoding = eteUTF16BE;
			return "readTextEncFrstBytTermStr:eteUTF16BE(2)";
		case 3:
			lastdecoding = eteUTF8;
			return "readTextEncFrstBytTermStr:eteUTF8(3)";
		}
		
	}
	
	//============================================================================================================================
	//  Read text where encoded first byte defines encoding with an unterminated string.
	//============================================================================================================================
	String ConsumeTextEncFrstBytUntermStr(int nofchar) {
		byte encoding;
		if (!ConsumeBuf(1, &encoding)) {
			tagDump << "::Error:Consuming terminated string failed:";
			return String::GetVoid();
		}
		
		switch (encoding) {
		case 0:
			lastdecoding = eteISO88591;
			return ConsumeUntermStr(nofchar-1); // Deduct encoding byte
		case 1:
			lastdecoding = eteUTF16;
			return ConsumeUntermStrUTF16(nofchar-1);
		case 2:
			lastdecoding = eteUTF16BE;
			return "readTextEncFrstBytTermStr:eteUTF16BE(2)";
		case 3:
			lastdecoding = eteUTF8;
			return "readTextEncFrstBytTermStr:eteUTF8(3)";
		}
		
	}
	
	//============================================================================================================================
	//  Read 2 strings where encoded first byte defines encoding followed by a null-terminated string.
	//============================================================================================================================
	bool ConsumeTextEncFrstByt2Str(int bytes, String &str1, String &str2) {
		byte encoding;
		if (!ConsumeBuf(1, &encoding)) {
			tagDump << "::Error:Consuming terminated string failed:";
			return false;
		}
		
		int remainingbytes = bytes - 1;
		switch (encoding) {
		case 0:
			lastdecoding = eteISO88591;
			str1 = ConsumeTermStr(remainingbytes);
			remainingbytes-= (strlen(str1)+1);
			str2 = ConsumeUntermStr(remainingbytes);
			return true;
		case 1:
			lastdecoding = eteUTF16;
			str1 = ConsumeTermStrUTF16(remainingbytes);
			remainingbytes-= ((str1.GetLength() * 2) + 2);
			str2 = ConsumeUntermStrUTF16(remainingbytes); // + NullTerm Pair
			return true;
		case 2:
			lastdecoding = eteUTF16BE;
			return "readTextEncFrstBytTermStr:eteUTF16BE(2)";
		case 3:
			lastdecoding = eteUTF8;
			return "readTextEncFrstBytTermStr:eteUTF8(3)";
		}
	
	}
	
	//============================================================================================================================
	//  Read a null-terminated string into a Container (ASCII only), Not always null-terminated, then up to number of characters.
	//============================================================================================================================
	String ConsumeTermStr(int nofchar) {
		byte rb[nofchar + 1];
		ConsumeBuf(nofchar, rb, 
			true // Look for Null term
			);
		
		rb[nofchar] = 0;
		return ToCharset(CHARSET_UTF8, (const char *)rb, CHARSET_ISO8859_1);	}
	
	//============================================================================================================================
	//  Read an unterminated string into a Container (ASCII only)
	//============================================================================================================================
	String ConsumeUntermStr(int nofchar) {
		byte rb[nofchar+1];
		ConsumeBuf(nofchar, rb, 
			false // Don't look for terminator
			);
/*
		for (int i=0; i < nofchar; i++) {
			if (rb[i] == 0xFF && i+1 < nofchar && rb[i+1] == 0xFF && i+2 < nofchar && rb[i+2] == 0xFF && i+3 < nofchar && rb[i+3] == 0xF6) {
				tagDump << "Found Götz character!(UTF8)";
			}
		}
		for (int i=0; i < nofchar; i++) {
			if (rb[i] == 0xF6) {
				tagDump << "Found Götz character!(singular) prev was " << rb[i-1];
			}
		}
*/
		
		// Convert "Götz" to "Gotz"!! This would be good to create search columns in db so you could search for either. i.e., search for "Gotz" and find "Götz"
		//ToAscii((char *)rb, nofchar, (Upp::byte)CHARSET_ISO8859_1); // from CharSet.i
		StringBuffer result(nofchar);
		//ConvertCharset(result, CHARSET_UTF8, (const char *)rb, CHARSET_ISO8859_1, nofchar);
		rb[nofchar] = 0;
		return ToCharset(CHARSET_UTF8, (const char *)rb, CHARSET_ISO8859_1);
	}

	//============================================================================================================================
	//  Read an unterminated UTF16 string into a Container (with BOM)
	//============================================================================================================================
	String ConsumeTermStrUTF16(int nofchar) {
		byte rb[nofchar];
		ConsumeBuf(nofchar, rb, // Needs to scan for 2 zeros!
		true // Scan!
		, false // Don't zap weird values
		, true // Scan for 2 zeros
		);
		
		int wnofchar = nofchar / 2;
		// Skip BOM, convert frame size to 16-bit words, take extra character off back end (2-byte character)
		String ss = FromUnicodeBuffer((wchar *)(rb + 2), wnofchar - 1);
		for (int i = 0; i < nofchar; i+=2) {
			if (!rb[i] && !rb[i+1]) {
				ss.Trim(i/2);
				break;
			}
		}
		return TrimLeft(TrimRight(ss)); // Almost all of these string have trailing spaces
	}

	//============================================================================================================================
	//  Read an unterminated UTF16 string into a Container (with BOM)
	//============================================================================================================================
	String ConsumeUntermStrUTF16(int nofchar) {
		byte rb[nofchar];
		ConsumeBuf(nofchar, rb, 
		false // Don't scan for null characters, not terminated
		);
		
		int wnofchar = nofchar / 2;
		// Skip BOM, convert frame size to 16-bit words, take extra character off back end (2-byte character)
		String ss = FromUnicodeBuffer((wchar *)(rb + 2), wnofchar - 1);

/*
		String s(p, nofchar);
		if (((s[0]&0xFF) == 0xFF) && ((s[1]&0xFF) == 0xFE))					// UTF16 Little Endian
			s = ToUtf8(s.Mid(2).ToWString());
		else if (((s[0]&0xFF) == 0xEF) && ((s[1]&0xFF) == 0xBB) && ((s[2]&0xFF) == 0xBF))	// UTF8
			s = s.Mid(3);
*/
		return TrimLeft(TrimRight(ss)); // Almost all of these string have trailing spaces
	}

	//============================================================================================================================
	String GetTagName(int tagno) {
		return tagNames[tagno];
	}
};

/*
Vorbis metadata, called Vorbis comments, supports metadata tags similar to those implemented in the ID3 standard for MP3. The metadata is stored in a vector of eight-bit-clean strings of arbitrary length and size. The size of the vector and the size of each string in bytes is limited to 232-1 (about 4.3 billion, or any positive integer that can be expressed in 32 bits). This vector is stored in the second header packet that begins a Vorbis bitstream.[42]

The strings are assumed to be encoded as UTF-8. Music tags are typically implemented as strings of the form "[TAG]=[VALUE]", for instance, "ARTIST=The John Smith Band". The tags are case-insensitive, thus typing "ARTIST=The John Smith Band" would be the same as "artist=The John Smith Band". Like the current version of ID3, users and encoding software are free to use whichever tags are appropriate for the content. For example, an encoder could use localized tag labels, live music tracks might contain a "Venue=" tag or files could have multiple genre definitions. Most applications also support common de facto standards such as discnumber and Replay Gain information.
A Vorbis tag is a list of fields in the format FieldName=Data. The field name can be composed of printable ASCII characters, 0x20 (space) through 0x7D ('}'), with 0x3D ('=') and 0x7E ('~') excluded. It is case insensitive, so artist and ARTIST are the same field. The number of fields and their length is restricted to 4,294,967,295 (the maximum value of a 32-bit integer), but most tag editing applications impose stricter limits.
*/
#endif